package com.github.ghsea.sso.client.security.shiro;

import com.github.ghsea.sso.client.common.StringUtils;
import com.github.ghsea.sso.client.common.SerializationUtils;
import com.github.ghsea.sso.client.common.SsoJedisPool;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;
import java.util.function.Function;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;

/**
 * @author guhai
 * @since 2021/6/10 16:49
 */
public class JedisCache<K, V> implements Cache<K, V> {

    private Logger logger = LoggerFactory.getLogger(getClass());

    private String cacheKeyName;

    private SsoJedisPool redisPool;

    /**
     * @param cacheKeyName 对于权限的缓存，其cacheKeyName是RealmClass的完整名称.authorizationCache。例如:
     *                     ExtCasRealm.authorizationCache
     * @param redisPool    redis连接池
     */
    public JedisCache(String cacheKeyName, SsoJedisPool redisPool) {
        this.cacheKeyName = cacheKeyName;
        this.redisPool = redisPool;
    }

    /**
     * @param key 取用户权限时，传进来的key是一个com.github.ghsea.sso.security.cas.UserPrincipal对象
     * @return
     * @throws CacheException
     */
    @SuppressWarnings("unchecked")
    @Override
    public V get(K key) throws CacheException {
        if (key == null) {
            return null;
        }

        return (V) executeRedisCommand((t) -> {
            byte[] bytes = t.hget(toBytes(cacheKeyName), toBytes(key));
            return SerializationUtils.unserialize(bytes);
        });
    }

    /**
     * @param key   UserPrincipal对象
     * @param value 取权限时，value是存于redis的SimpleAuthenticationInfo对象（ExtCasRealm.doGetAuthorizationInfo()方法返回的）
     * @return
     * @throws CacheException
     */
    @SuppressWarnings("unchecked")
    @Override
    public V put(K key, V value) throws CacheException {
        if (key == null) {
            return null;
        }

        return (V) executeRedisCommand((redis) -> {
            byte[] cacheKey = toBytes(cacheKeyName);
            redis.hset(cacheKey, toBytes(key), toBytes(value));
            //改造后添加了失效时间为 30分钟.给了过期时间
            redis.expire(cacheKey, 1800);
            return value;
        });
    }

    @SuppressWarnings("unchecked")
    @Override
    public V remove(K key) throws CacheException {
        if (key == null) {
            return null;
        }

        return (V) executeRedisCommand((redis) -> {
            byte[] hashCacheKey = toBytes(cacheKeyName);
            byte[] fieldKey = toBytes(key);

            V cachedValue = (V) toObject(redis.hget(hashCacheKey, fieldKey));
            if (cachedValue != null) {
                redis.hdel(hashCacheKey, fieldKey);
            }
            return cachedValue;
        });
    }

    @Override
    public void clear() throws CacheException {
        executeRedisCommand((redis) -> {
            redis.hdel(toBytes(cacheKeyName));
            return null;
        });
    }

    @Override
    public int size() {
        return (Integer) executeRedisCommand((redis) -> redis.hlen(toBytes(cacheKeyName)).intValue());
    }

    @SuppressWarnings("unchecked")
    @Override
    public Set<K> keys() {
        return (Set) executeRedisCommand((jedis) -> {
            Set<K> keys = Sets.newHashSet();
            Set<byte[]> set = jedis.hkeys(toBytes(cacheKeyName));
            for (byte[] key : set) {
                Object obj = toObject(key);
                if (obj != null) {
                    keys.add((K) obj);
                }
            }
            return keys;
        });
    }

    @SuppressWarnings("unchecked")
    @Override
    public Collection<V> values() {
        return (Collection) executeRedisCommand((redis) -> {
            Collection<V> vals = new ArrayList<>();
            Collection<byte[]> col = redis.hvals(toBytes(cacheKeyName));
            for (byte[] val : col) {
                Object obj = toObject(val);
                if (obj != null) {
                    vals.add((V) obj);
                }
            }
            return vals;
        });
    }

    private Object executeRedisCommand(Function<Jedis, Object> function) {
        try (Jedis jedis = redisPool.getResource()) {
            return function.apply(jedis);
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException(e);
        }
    }

    private byte[] toBytes(Object object) {
        if (object instanceof String) {
            return StringUtils.getBytes((String) object);
        } else {
            return SerializationUtils.serialize(object);
        }
    }

    private Object toObject(byte[] bytes) {
        return SerializationUtils.unserialize(bytes);
    }
}
